/******************************************************************************
*                        ETSI TS 103 634 V1.1.1                               *
*              Low Complexity Communication Codec Plus (LC3plus)              *
*                                                                             *
* Copyright licence is solely granted through ETSI Intellectual Property      *
* Rights Policy, 3rd April 2019. No patent licence is granted by implication, *
* estoppel or otherwise.                                                      *
******************************************************************************/

#ifndef __TINYWAVEIN_C_H__
#define __TINYWAVEIN_C_H__

/*#define SUPPORT_BWF*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if defined(__i386__) || defined(_M_IX86) || defined(__x86_64__) ||            \
    defined(_M_X64) || defined(__arm__) || defined(__aarch64__)
#define __TWI_LE /* _T_iny _W_ave _I_n _L_ittle _E_ndian */
#endif

#if defined(__POWERPC__)
#define __TWI_BE /* _T_iny _W_ave _I_n _B_ig _E_ndian */
#endif

#if !defined(__TWI_LE) && !defined(__TWI_BE)
#error unknown processor
#endif

#define __TWI_SUCCESS (0)
#define __TWI_ERROR (-1)

#ifdef SUPPORT_BWF
typedef struct {
  float loudnessVal;
  float loudnessRange;
  float maxTruePeakLevel;
  float maxMomentaryLoudnes;
  float maxShortTermLoudness;
} WAVEIN_LOUDNESSINFO;
#endif

typedef struct __tinyWaveInHandle {
  FILE *theFile;
  fpos_t dataChunkPos;
  LC3_UINT32 position;
  LC3_UINT32 length;
  LC3_UINT32 bps;
#ifdef SUPPORT_BWF
  WAVEIN_LOUDNESSINFO *loudnessInfo;
#endif
} __tinyWaveInHandle, WAVEFILEIN;

typedef struct {
  LC3_INT16 compressionCode;
  LC3_INT16 numberOfChannels;
  LC3_UINT32 sampleRate;
  LC3_UINT32 averageBytesPerSecond;
  LC3_INT16 blockAlign;
  LC3_INT16 bitsPerSample;
  /* LC3_INT16 extraFormatBytes ; */
} SWavInfo;

#ifdef SUPPORT_BWF
typedef struct {
  LC3_UCHAR description[256];
  LC3_UCHAR originator[32];
  LC3_UCHAR originatorReference[32];
  LC3_UCHAR originatorDate[10]; /* ASCII: <<yyyy:mm:dd>> */
  LC3_UCHAR originationTime[8]; /* ASCII: <<hh:mm:ss>> */
  LC3_UINT32 timeReferenceLow;
  LC3_UINT32 timeReferenceHigh;
  LC3_UINT16 version;
  LC3_UCHAR UMID[64]; /* Binary Bytes of SMPTE UMID */

  LC3_INT16 loudnessVal;
  LC3_INT16 loudnessRange;
  LC3_INT16 maxTruePeakLevel;
  LC3_INT16 maxMomentaryLoudnes;
  LC3_INT16 maxShortTermLoudness;

  LC3_UCHAR Reserved[180];

  LC3_UCHAR codingHistory; /* ASCII: <<History coding>> */
} SBwfWav;
#endif

typedef struct {
  char chunkID[4];
  LC3_UINT32 chunkSize;
  /* long dataOffset ; */ /* never used */
} SChunk;

/* local wrapper, always returns correct endian */
static size_t fread_LE(void *ptr, size_t size, size_t nmemb, FILE *stream);

#ifdef __TWI_BE
static LC3_INT16 BigEndian16(LC3_INT16 v);
static LC3_INT32 BigEndian32(LC3_INT32 v);
#endif

/*!
 *  \brief Read header from a WAVEfile. Host endianess is handled accordingly.
 *  \fp filepointer of type FILE*.
 *  \wavinfo SWavInfo struct where the decoded header info is stored into.
 *  \return 0 on success and non-zero on failure.
 *
 */
static WAVEFILEIN *OpenWav(const char *filename, LC3_UINT32 *samplerate,
                           LC3_INT16 *channels, LC3_UINT32 *samplesInFile,
                           LC3_INT16 *bps) {
  WAVEFILEIN *self;

  SChunk fmt_chunk, data_chunk;
  LC3_INT32 offset;
  LC3_UINT32 tmpSize;
  char tmpFormat[4];
  SWavInfo wavinfo = {0, 0, 0, 0, 0, 0};

  self = (WAVEFILEIN *)calloc(1, sizeof(WAVEFILEIN));
  if (!self)
    goto bail; /* return NULL; */

  if (!filename)
    goto bail;
  if (!samplerate)
    goto bail;
  if (!channels)
    goto bail;
  if (!samplesInFile)
    goto bail;
  if (!bps)
    goto bail;

  self->theFile = fopen(filename, "rb");
  if (!self->theFile)
    goto bail;

  /* read RIFF-chunk */
  if (fread(tmpFormat, 1, 4, self->theFile) != 4) {
    goto bail;
  }

  if (strncmp("RIFF", tmpFormat, 4)) {
    goto bail;
  }

  /* Read RIFF size. Ignored. */
  fread_LE(&tmpSize, 4, 1, self->theFile);

  /* read WAVE-chunk */
  if (fread(tmpFormat, 1, 4, self->theFile) != 4) {
    goto bail;
  }

  if (strncmp("WAVE", tmpFormat, 4)) {
    goto bail;
  }

  /* read format/bext-chunk */
  if (fread(fmt_chunk.chunkID, 1, 4, self->theFile) != 4) {
    goto bail;
  }

#ifdef SUPPORT_BWF
  /* test for bext-chunk */
  if (!strncmp("bext", fmt_chunk.chunkID, 4)) {
    /*LC3_UINT32 i;*/
    LC3_UINT32 bextSize = 0;

    if (fread_LE(&bextSize, 1, 4, self->theFile) != 4) {
      goto bail;
    }

    self->loudnessInfo =
        (WAVEIN_LOUDNESSINFO *)calloc(1, sizeof(WAVEIN_LOUDNESSINFO));

    if (bextSize >= 602) { /* minimum size bext-data, w/o 'CodingHistory' */
      LC3_INT32 i;
      LC3_INT16 readBuf = 0;
      LC3_INT32 nulbuf = 0;

      /* first skip all descriptive data */
      for (i = 0; i < 412; i++) {
        if (fread_LE(&nulbuf, 1, 1, self->theFile) != 1) {
          goto bail;
        }
        bextSize -= 1;
      }
      /* second, read loudness data */
      fread_LE(&readBuf, 2, 1, self->theFile);
      bextSize -= 2;
      self->loudnessInfo->loudnessVal = (float)readBuf * 0.01f;

      fread_LE(&readBuf, 2, 1, self->theFile);
      bextSize -= 2;
      self->loudnessInfo->loudnessRange = (float)readBuf * 0.01f;

      fread_LE(&readBuf, 2, 1, self->theFile);
      bextSize -= 2;
      self->loudnessInfo->maxTruePeakLevel = (float)readBuf * 0.01f;

      fread_LE(&readBuf, 2, 1, self->theFile);
      bextSize -= 2;
      self->loudnessInfo->maxMomentaryLoudnes = (float)readBuf * 0.01f;

      fread_LE(&readBuf, 2, 1, self->theFile);
      bextSize -= 2;
      self->loudnessInfo->maxShortTermLoudness = (float)readBuf * 0.01f;

      /* skip reserved data */
      for (i = 0; i < 180; i++) {
        if (fread_LE(&nulbuf, 1, 1, self->theFile) != 1) {
          goto bail;
        }
        bextSize -= 1;
      }
    }

    /* skip remaining data */
    while (bextSize) {
      LC3_INT32 nulbuf;
      if (fread_LE(&nulbuf, 1, 1, self->theFile) != 1) {
        goto bail;
      }
      bextSize -= 1;
    }

    /* read next chunk header */
    if (fread(fmt_chunk.chunkID, 1, 4, self->theFile) != 4) {
      goto bail;
    }
  }
#endif

  /* skip some potential chunks up to fmt chunk */
  
  while (strncmp("fmt ", fmt_chunk.chunkID, 4) != 0) {
    LC3_UINT32 chunkSize = 0;

    if (fread_LE(&chunkSize, 1, 4, self->theFile) != 4) {
      goto bail;
    }

    /* skip chunk data */
    while (chunkSize) {
      LC3_INT32 nulbuf;
      if (fread_LE(&nulbuf, 1, 1, self->theFile) != 1) {
        goto bail;
      }
      chunkSize -= 1;
    }

    /* read next chunk header */
    if (fread(fmt_chunk.chunkID, 1, 4, self->theFile) != 4) {
      goto bail;
    }
  }

  /* go on with fmt-chunk */
  if (strncmp("fmt ", fmt_chunk.chunkID, 4)) {
    goto bail;
  }

  if (fread_LE(&fmt_chunk.chunkSize, 4, 1, self->theFile) !=
      1) { /* should be 16 for PCM-format (uncompressed) */
    goto bail;
  }


  /* read  info */
  fread_LE(&(wavinfo.compressionCode), 2, 1, self->theFile);
  fread_LE(&(wavinfo.numberOfChannels), 2, 1, self->theFile);
  fread_LE(&(wavinfo.sampleRate), 4, 1, self->theFile);
  fread_LE(&(wavinfo.averageBytesPerSecond), 4, 1, self->theFile);
  fread_LE(&(wavinfo.blockAlign), 2, 1, self->theFile);
  fread_LE(&(wavinfo.bitsPerSample), 2, 1, self->theFile);

  if (wavinfo.compressionCode == -2) {
    fseek(self->theFile, 8, SEEK_CUR); // skip channel mask
    fread_LE(&(wavinfo.compressionCode), 2, 1, self->theFile); // part of GUID
    fseek(self->theFile, 14, SEEK_CUR); // skip rest of GUID
    offset = fmt_chunk.chunkSize - 40;
  } else
    offset = fmt_chunk.chunkSize - 16;

  if (wavinfo.compressionCode == 0x01) {
    if ((wavinfo.bitsPerSample != 16) && (wavinfo.bitsPerSample != 24) &&
        (wavinfo.bitsPerSample != 32))
      /* we do only support 16,24 and 32 bit PCM audio */
      goto bail;
  } else {
    /* if(wavinfo.bitsPerSample != 32) */
    printf("compressioncode: %02x\n", wavinfo.compressionCode);
    puts("Error! We only support 16,24 and 32 bit PCM audio");
    exit(1);
    goto bail;
  }

  /* Skip rest of fmt header if any. */
  for (; offset > 0; offset--) {
    fread(&tmpSize, 1, 1, self->theFile);
  }

  do {

    /* Read data chunk ID */
    if (fread(data_chunk.chunkID, 1, 4, self->theFile) != 4) {
      goto bail;
    }

    /* Read chunk length. */

    if (fread_LE(&offset, 4, 1, self->theFile) != 1) {
      goto bail;
    }

    /* Check for data chunk signature. */
    if (strncmp("data", data_chunk.chunkID, 4) == 0) {
      data_chunk.chunkSize = offset;
      break;
    }

    /* unused 1 byte present, if size is odd */
    /* see https://www.daubnet.com/en/file-format-riff */
    if (offset % 2) {
      offset++;
    }

    /* Jump over non data chunk. */
    for (; offset > 0; offset--) {
      fread(&tmpSize, 1, 1, self->theFile);
    }

  } while (!feof(self->theFile));

  /* success so far */
  *samplerate = wavinfo.sampleRate;
  *channels = wavinfo.numberOfChannels;
  *samplesInFile = data_chunk.chunkSize / wavinfo.numberOfChannels;
  *samplesInFile /= ((wavinfo.bitsPerSample + 7) / 8);
  *bps = wavinfo.bitsPerSample;

  self->position = 0;
  self->bps = wavinfo.bitsPerSample;
  self->length = *samplesInFile * wavinfo.numberOfChannels;

  fgetpos(self->theFile, &self->dataChunkPos);

  return self;

bail:
  free(self);
  return NULL;
}

#ifdef SUPPORT_BWF
static void ReadBWF(WAVEFILEIN *self, WAVEIN_LOUDNESSINFO **wavInLoudness) {
  *wavInLoudness = self->loudnessInfo;
}
#endif

static LC3_INT32 __ReadSample16(WAVEFILEIN *self, LC3_INT32 *sample, LC3_INT32 scale) {
  size_t cnt;
  LC3_INT16 v = 0;

  cnt = fread(&v, 2, 1, self->theFile);

  if (cnt != 1) {
    return __TWI_ERROR;
  }

  self->position += 1;

#ifdef __TWI_BE
  v = BigEndian16(v);
#endif

  if ((scale - 16) > 0)
    *sample = v << (scale - 16);
  else
    *sample = v >> (16 - scale);

  return __TWI_SUCCESS;
}

static LC3_INT32 __ReadSample24(WAVEFILEIN *self, LC3_INT32 *sample, LC3_INT32 scale) {
  size_t cnt;
  LC3_INT32 v = 0;

  cnt = fread(&v, 3, 1, self->theFile);

  if (cnt != 1) {
    return __TWI_ERROR;
  }

  self->position += 1;

#ifdef __TWI_BE
  v = BigEndian32(v);
#endif

  if (v >= 0x800000) {
    v |= 0xff000000;
  }

  if ((scale - 24) > 0)
    *sample = v << (scale - 24);
  else
    *sample = v >> (24 - scale);

  return __TWI_SUCCESS;
}

static LC3_INT32 __ReadSample32(WAVEFILEIN *self, LC3_INT32 *sample) {
  size_t cnt;
  LC3_INT32 v = 0;

  cnt = fread(&v, 4, 1, self->theFile);

  if (cnt != 1) {
    return __TWI_ERROR;
  }

  self->position += 1;

#ifdef __TWI_BE
  v = BigEndian32(v);
#endif

  *sample = v;

  return __TWI_SUCCESS;
}

static LC3_INT32 __ReadSampleInternal(WAVEFILEIN *self, LC3_INT32 *sample, LC3_INT32 scale) {
  LC3_INT32 err;

  if (!self) {
    return __TWI_ERROR;
  }

  switch (self->bps) {

  case 16:
    err = __ReadSample16(self, sample, scale);
    break;

  case 24:
    err = __ReadSample24(self, sample, scale);
    break;

  default:
    err = __TWI_ERROR;
    break;
  }

  return err;
}

/* not fully tested */
/* this function returns normalized values in the range +32767..-32768 */
/* static LC3_INT32 ReadWavShort(
                        WAVEFILEIN*   self,
                        LC3_INT16         sampleBuffer[],
                        LC3_UINT32  nSamplesToRead,
                        LC3_UINT32* nSamplesRead
                        )
{
  LC3_UINT32 i;
  LC3_INT32 err = __TWI_SUCCESS;

  if (!sampleBuffer) {
    return __TWI_ERROR;
  }
  if (self->position + nSamplesToRead > self->length) {
    nSamplesToRead = self->length - self->position;
  }

  for (i=0; i< nSamplesToRead; i++) {
    if(self->bps == 32)
    {
      LC3_INT32 tmp;
      err = __ReadSample32(self, &tmp);
      if (err != __TWI_SUCCESS) {
        return err;
      }
      sampleBuffer[i] = (LC3_INT32)(tmp * 32768.0f);
    }
    else
    {
      LC3_INT32 tmp;
      err = __ReadSampleInternal(self, &tmp, 16);
      if (err != __TWI_SUCCESS) {
        return err;
      }
      sampleBuffer[i] = (LC3_INT16)tmp;
    }
    *nSamplesRead += 1;
  }

  return __TWI_SUCCESS;
}
*/

/* not fully tested */
/* this function returns normalized values in the range +8388607..-8388608 */
static LC3_INT32 ReadWavInt(WAVEFILEIN *self, LC3_INT32 sampleBuffer[],
                      LC3_UINT32 nSamplesToRead, LC3_UINT32 *nSamplesRead) {
  LC3_UINT32 i;
  LC3_INT32 err = __TWI_SUCCESS;
  *nSamplesRead = 0;

  if (!sampleBuffer) {
    return __TWI_ERROR;
  }

  /* check if we have enough samples left, if not,
     set nSamplesToRead to number of samples left. */
  if (self->position + nSamplesToRead > self->length) {
    nSamplesToRead = self->length - self->position;
  }

  for (i = 0; i < nSamplesToRead; i++) {
    if (self->bps == 32) {
      LC3_INT32 tmp;
      err = __ReadSample32(self, &tmp);
      // conversion to 24bit
      tmp = tmp >> 8;

      if (err != __TWI_SUCCESS) {
        return err;
      }
      sampleBuffer[i] = tmp;
    } else {
      LC3_INT32 tmp;
      err = __ReadSampleInternal(self, &tmp, self->bps);
      if (err != __TWI_SUCCESS) {
        return err;
      }
      sampleBuffer[i] = tmp;
    }
    *nSamplesRead += 1;
  }

  return __TWI_SUCCESS;
}

static LC3_INT32 CloseWavIn(WAVEFILEIN *self) {
  if (self) {
    if (self->theFile) {
      fclose(self->theFile);
    }
  }
  free(self);

  return __TWI_SUCCESS;
}
/*
static LC3_INT32 ResetWavIn(WAVEFILEIN* self)
{
  if (self) {
    if (self->theFile) {
        fsetpos(self->theFile, &self->dataChunkPos);
        self->position = 0;
    }
  }
  return __TWI_SUCCESS;
}
*/
/*------------- local subs ----------------*/

static size_t fread_LE(void *ptr, size_t size, size_t nmemb, FILE *stream) {
#ifdef __TWI_LE
  return fread(ptr, size, nmemb, stream);
#endif
#ifdef __TWI_BE

  LC3_UCHAR x[sizeof(LC3_INT32)];
  LC3_UCHAR *y = (LC3_UCHAR *)ptr;
  LC3_INT32 i;
  LC3_INT32 len;

  len = fread(x, size, nmemb, stream);

  for (i = 0; i < size * nmemb; i++) {
    *y++ = x[size * nmemb - i - 1];
  }

  return len;
#endif
}

#ifdef __TWI_BE
static LC3_INT16 BigEndian16(LC3_INT16 v) {
  LC3_INT16 a = (v & 0x0ff);
  LC3_INT16 b = (v & 0x0ff00) >> 8;

  return a << 8 | b;
}

static LC3_INT32 BigEndian32(LC3_INT32 v) {
  LC3_INT32 a = (v & 0x0ff);
  LC3_INT32 b = (v & 0x0ff00) >> 8;
  LC3_INT32 c = (v & 0x0ff0000) >> 16;
  LC3_INT32 d = (v & 0xff000000) >> 24;

  return a << 24 | b << 16 | c << 8 | d;
}
#endif

#endif /* __TINYWAVEIN_C_H__ */
